From d89cd9037ed46438e5c694782bc6230a9db61232 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Tue, 29 Aug 2017 22:55:31 -0700 Subject: [PATCH] Alternate registries. Allow users to add dependencies which are in alternate registries. The alternate registries are listed in the .cargo/config, and must there have a link to a conforming crate index. --- src/bin/login.rs | 2 +- src/cargo/core/features.rs | 5 +- src/cargo/core/source/mod.rs | 157 ++++++++++++ .../core/{source.rs => source/source_id.rs} | 225 +++++------------- src/cargo/ops/registry.rs | 19 +- src/cargo/sources/registry/mod.rs | 2 +- src/cargo/sources/registry/remote.rs | 3 +- src/cargo/util/config.rs | 2 +- src/cargo/util/toml/mod.rs | 30 ++- tests/alt-registry.rs | 145 +++++++++++ tests/bad-config.rs | 6 +- tests/cargotest/support/registry.rs | 30 ++- 12 files changed, 429 insertions(+), 197 deletions(-) create mode 100644 src/cargo/core/source/mod.rs rename src/cargo/core/{source.rs => source/source_id.rs} (76%) create mode 100644 tests/alt-registry.rs diff --git a/src/bin/login.rs b/src/bin/login.rs index c41118cc3..5e8e0e2db 100644 --- a/src/bin/login.rs +++ b/src/bin/login.rs @@ -51,7 +51,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult { let mut src = RegistrySource::remote(&src, config); src.update()?; let config = src.config()?.unwrap(); - let host = options.flag_host.clone().unwrap_or(config.api); + let host = options.flag_host.clone().unwrap_or(config.api.unwrap()); println!("please visit {}me and paste the API Token below", host); let mut line = String::new(); let input = io::stdin(); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 4bfafc174..d4e91c010 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -31,7 +31,7 @@ //! })?; //! ``` //! -//! Notably you'll notice the `require` funciton called with your `Feature`, and +//! Notably you'll notice the `require` function called with your `Feature`, and //! then you use `chain_err` to tack on more context for why the feature was //! required when the feature isn't activated. //! @@ -122,6 +122,9 @@ features! { // A dummy feature that gates the usage of the `im-a-teapot` manifest // entry. This is basically just intended for tests. [unstable] test_dummy_unstable: bool, + + // Downloading packages from alternative registry indexes. + [unstable] alternative_registries: bool, } } diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs new file mode 100644 index 000000000..bc54fa184 --- /dev/null +++ b/src/cargo/core/source/mod.rs @@ -0,0 +1,157 @@ +use std::collections::hash_map::{HashMap, Values, IterMut}; + +use core::{Package, PackageId, Registry}; +use util::CargoResult; + +mod source_id; + +pub use self::source_id::{SourceId, GitReference}; + +/// A Source finds and downloads remote packages based on names and +/// versions. +pub trait Source: Registry { + /// Returns the `SourceId` corresponding to this source + fn source_id(&self) -> &SourceId; + + /// The update method performs any network operations required to + /// get the entire list of all names, versions and dependencies of + /// packages managed by the Source. + fn update(&mut self) -> CargoResult<()>; + + /// The download method fetches the full package for each name and + /// version specified. + fn download(&mut self, package: &PackageId) -> CargoResult; + + /// Generates a unique string which represents the fingerprint of the + /// current state of the source. + /// + /// This fingerprint is used to determine the "fresheness" of the source + /// later on. It must be guaranteed that the fingerprint of a source is + /// constant if and only if the output product will remain constant. + /// + /// The `pkg` argument is the package which this fingerprint should only be + /// interested in for when this source may contain multiple packages. + fn fingerprint(&self, pkg: &Package) -> CargoResult; + + /// If this source supports it, verifies the source of the package + /// specified. + /// + /// Note that the source may also have performed other checksum-based + /// verification during the `download` step, but this is intended to be run + /// just before a crate is compiled so it may perform more expensive checks + /// which may not be cacheable. + fn verify(&self, _pkg: &PackageId) -> CargoResult<()> { + Ok(()) + } +} + +impl<'a, T: Source + ?Sized + 'a> Source for Box { + /// Forwards to `Source::source_id` + fn source_id(&self) -> &SourceId { + (**self).source_id() + } + + /// Forwards to `Source::update` + fn update(&mut self) -> CargoResult<()> { + (**self).update() + } + + /// Forwards to `Source::download` + fn download(&mut self, id: &PackageId) -> CargoResult { + (**self).download(id) + } + + /// Forwards to `Source::fingerprint` + fn fingerprint(&self, pkg: &Package) -> CargoResult { + (**self).fingerprint(pkg) + } + + /// Forwards to `Source::verify` + fn verify(&self, pkg: &PackageId) -> CargoResult<()> { + (**self).verify(pkg) + } +} + +/// A `HashMap` of `SourceId` -> `Box` +#[derive(Default)] +pub struct SourceMap<'src> { + map: HashMap>, +} + +/// A `std::collection::hash_map::Values` for `SourceMap` +pub type Sources<'a, 'src> = Values<'a, SourceId, Box>; + +/// A `std::collection::hash_map::IterMut` for `SourceMap` +pub struct SourcesMut<'a, 'src: 'a> { + inner: IterMut<'a, SourceId, Box>, +} + +impl<'src> SourceMap<'src> { + /// Create an empty map + pub fn new() -> SourceMap<'src> { + SourceMap { map: HashMap::new() } + } + + /// Like `HashMap::contains_key` + pub fn contains(&self, id: &SourceId) -> bool { + self.map.contains_key(id) + } + + /// Like `HashMap::get` + pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> { + let source = self.map.get(id); + + source.map(|s| { + let s: &(Source + 'src) = &**s; + s + }) + } + + /// Like `HashMap::get_mut` + pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> { + self.map.get_mut(id).map(|s| { + let s: &mut (Source + 'src) = &mut **s; + s + }) + } + + /// Like `HashMap::get`, but first calculates the `SourceId` from a + /// `PackageId` + pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> { + self.get(pkg_id.source_id()) + } + + /// Like `HashMap::insert`, but derives the SourceId key from the Source + pub fn insert(&mut self, source: Box) { + let id = source.source_id().clone(); + self.map.insert(id, source); + } + + /// Like `HashMap::is_empty` + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Like `HashMap::len` + pub fn len(&self) -> usize { + self.map.len() + } + + /// Like `HashMap::values` + pub fn sources<'a>(&'a self) -> Sources<'a, 'src> { + self.map.values() + } + + /// Like `HashMap::iter_mut` + pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> { + SourcesMut { inner: self.map.iter_mut() } + } +} + +impl<'a, 'src> Iterator for SourcesMut<'a, 'src> { + type Item = (&'a SourceId, &'a mut (Source + 'src)); + fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> { + self.inner.next().map(|(a, b)| (a, &mut **b)) + } +} + diff --git a/src/cargo/core/source.rs b/src/cargo/core/source/source_id.rs similarity index 76% rename from src/cargo/core/source.rs rename to src/cargo/core/source/source_id.rs index 01d659919..1c37d3199 100644 --- a/src/cargo/core/source.rs +++ b/src/cargo/core/source/source_id.rs @@ -1,5 +1,4 @@ use std::cmp::{self, Ordering}; -use std::collections::hash_map::{HashMap, Values, IterMut}; use std::fmt::{self, Formatter}; use std::hash::{self, Hash}; use std::path::Path; @@ -11,76 +10,28 @@ use serde::ser; use serde::de; use url::Url; -use core::{Package, PackageId, Registry}; use ops; use sources::git; use sources::{PathSource, GitSource, RegistrySource, CRATES_IO}; use sources::DirectorySource; -use util::{Config, CargoResult, ToUrl}; +use util::{Config, ConfigValue as CV, CargoResult, ToUrl}; -/// A Source finds and downloads remote packages based on names and -/// versions. -pub trait Source: Registry { - /// Returns the `SourceId` corresponding to this source - fn source_id(&self) -> &SourceId; - - /// The update method performs any network operations required to - /// get the entire list of all names, versions and dependencies of - /// packages managed by the Source. - fn update(&mut self) -> CargoResult<()>; - - /// The download method fetches the full package for each name and - /// version specified. - fn download(&mut self, package: &PackageId) -> CargoResult; - - /// Generates a unique string which represents the fingerprint of the - /// current state of the source. - /// - /// This fingerprint is used to determine the "fresheness" of the source - /// later on. It must be guaranteed that the fingerprint of a source is - /// constant if and only if the output product will remain constant. - /// - /// The `pkg` argument is the package which this fingerprint should only be - /// interested in for when this source may contain multiple packages. - fn fingerprint(&self, pkg: &Package) -> CargoResult; - - /// If this source supports it, verifies the source of the package - /// specified. - /// - /// Note that the source may also have performed other checksum-based - /// verification during the `download` step, but this is intended to be run - /// just before a crate is compiled so it may perform more expensive checks - /// which may not be cacheable. - fn verify(&self, _pkg: &PackageId) -> CargoResult<()> { - Ok(()) - } +/// Unique identifier for a source of packages. +#[derive(Clone, Eq, Debug)] +pub struct SourceId { + inner: Arc, } -impl<'a, T: Source + ?Sized + 'a> Source for Box { - /// Forwards to `Source::source_id` - fn source_id(&self) -> &SourceId { - (**self).source_id() - } - - /// Forwards to `Source::update` - fn update(&mut self) -> CargoResult<()> { - (**self).update() - } - - /// Forwards to `Source::download` - fn download(&mut self, id: &PackageId) -> CargoResult { - (**self).download(id) - } - - /// Forwards to `Source::fingerprint` - fn fingerprint(&self, pkg: &Package) -> CargoResult { - (**self).fingerprint(pkg) - } - - /// Forwards to `Source::verify` - fn verify(&self, pkg: &PackageId) -> CargoResult<()> { - (**self).verify(pkg) - } +#[derive(Eq, Clone, Debug)] +struct SourceIdInner { + /// The source URL + url: Url, + /// `git::canonicalize_url(url)` for the url field + canonical_url: Url, + /// The source kind + kind: Kind, + // e.g. the exact git revision of the specified branch for a Git Source + precise: Option, } /// The possible kinds of code source. Along with a URL, this fully defines the @@ -91,7 +42,7 @@ enum Kind { Git(GitReference), /// represents a local path Path, - /// represents the central registry + /// represents a remote registry Registry, /// represents a local filesystem-based registry LocalRegistry, @@ -110,25 +61,6 @@ pub enum GitReference { Rev(String), } -/// Unique identifier for a source of packages. -#[derive(Clone, Eq, Debug)] -pub struct SourceId { - inner: Arc, -} - -/// Unique identifier for a source of packages. -#[derive(Eq, Clone, Debug)] -struct SourceIdInner { - /// The source URL - url: Url, - /// `git::canonicalize_url(url)` for the url field - canonical_url: Url, - /// The source kind - kind: Kind, - // e.g. the exact git revision of the specified branch for a Git Source - precise: Option, -} - impl SourceId { /// Create a SourceId object from the kind and url. /// @@ -250,11 +182,44 @@ impl SourceId { SourceId::for_registry(&url) } + pub fn alt_registry(config: &Config, key: &str) -> CargoResult { + let registries = config.get_table("registries")?; + match registries.as_ref().and_then(|registries| registries.val.get(key)) { + Some(registry) => { + let index = match *registry { + CV::Table(ref registry, _) => { + match registry.get("index") { + Some(index) => index.string(&format!("registries.{}", key))?.0, + None => return Err(format!("No index for registry `{}`", key).into()), + } + } + _ => registry.expected("table", &format!("registries.{}", key))? + }; + + let url = index.to_url()?; + + Ok(SourceId { + inner: Arc::new(SourceIdInner { + kind: Kind::Registry, + canonical_url: git::canonicalize_url(&url)?, + url: url, + precise: None, + }), + }) + } + None => Err(format!("Required unknown registry source: `{}`", key).into()) + } + } + /// Get this source URL pub fn url(&self) -> &Url { &self.inner.url } + pub fn display_registry(&self) -> String { + format!("registry `{}`", self.url()) + } + /// Is this source from a filesystem path pub fn is_path(&self) -> bool { self.inner.kind == Kind::Path @@ -262,7 +227,10 @@ impl SourceId { /// Is this source from a registry (either local or not) pub fn is_registry(&self) -> bool { - self.inner.kind == Kind::Registry || self.inner.kind == Kind::LocalRegistry + match self.inner.kind { + Kind::Registry | Kind::LocalRegistry => true, + _ => false, + } } /// Is this source from a git repository @@ -274,7 +242,7 @@ impl SourceId { } /// Creates an implementation of `Source` corresponding to this ID. - pub fn load<'a>(&self, config: &'a Config) -> CargoResult> { + pub fn load<'a>(&self, config: &'a Config) -> CargoResult> { trace!("loading SourceId; {}", self); match self.inner.kind { Kind::Git(..) => Ok(Box::new(GitSource::new(self, config)?)), @@ -411,7 +379,7 @@ impl fmt::Display for SourceId { } SourceIdInner { kind: Kind::Registry, ref url, .. } | SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => { - write!(f, "registry {}", url) + write!(f, "registry `{}`", url) } SourceIdInner { kind: Kind::Directory, ref url, .. } => { write!(f, "dir {}", url) @@ -546,89 +514,6 @@ impl<'a> fmt::Display for PrettyRef<'a> { } } -/// A `HashMap` of `SourceId` -> `Box` -#[derive(Default)] -pub struct SourceMap<'src> { - map: HashMap>, -} - -/// A `std::collection::hash_map::Values` for `SourceMap` -pub type Sources<'a, 'src> = Values<'a, SourceId, Box>; - -/// A `std::collection::hash_map::IterMut` for `SourceMap` -pub struct SourcesMut<'a, 'src: 'a> { - inner: IterMut<'a, SourceId, Box>, -} - -impl<'src> SourceMap<'src> { - /// Create an empty map - pub fn new() -> SourceMap<'src> { - SourceMap { map: HashMap::new() } - } - - /// Like `HashMap::contains_key` - pub fn contains(&self, id: &SourceId) -> bool { - self.map.contains_key(id) - } - - /// Like `HashMap::get` - pub fn get(&self, id: &SourceId) -> Option<&(Source + 'src)> { - let source = self.map.get(id); - - source.map(|s| { - let s: &(Source + 'src) = &**s; - s - }) - } - - /// Like `HashMap::get_mut` - pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut (Source + 'src)> { - self.map.get_mut(id).map(|s| { - let s: &mut (Source + 'src) = &mut **s; - s - }) - } - - /// Like `HashMap::get`, but first calculates the `SourceId` from a - /// `PackageId` - pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&(Source + 'src)> { - self.get(pkg_id.source_id()) - } - - /// Like `HashMap::insert`, but derives the SourceId key from the Source - pub fn insert(&mut self, source: Box) { - let id = source.source_id().clone(); - self.map.insert(id, source); - } - - /// Like `HashMap::is_empty` - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Like `HashMap::len` - pub fn len(&self) -> usize { - self.map.len() - } - - /// Like `HashMap::values` - pub fn sources<'a>(&'a self) -> Sources<'a, 'src> { - self.map.values() - } - - /// Like `HashMap::iter_mut` - pub fn sources_mut<'a>(&'a mut self) -> SourcesMut<'a, 'src> { - SourcesMut { inner: self.map.iter_mut() } - } -} - -impl<'a, 'src> Iterator for SourcesMut<'a, 'src> { - type Item = (&'a SourceId, &'a mut (Source + 'src)); - fn next(&mut self) -> Option<(&'a SourceId, &'a mut (Source + 'src))> { - self.inner.next().map(|(a, b)| (a, &mut **b)) - } -} - #[cfg(test)] mod tests { use super::{SourceId, Kind, GitReference}; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 42ff2f872..4d21be922 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -83,11 +83,18 @@ fn verify_dependencies(pkg: &Package, registry_src: &SourceId) a version", dep.name()) } } else if dep.source_id() != registry_src { - bail!("crates cannot be published to crates.io with dependencies sourced from \ - a repository\neither publish `{}` as its own crate on crates.io and \ - specify a crates.io version as a dependency or pull it into this \ - repository and specify it with a path and version\n(crate `{}` has \ - repository path `{}`)", dep.name(), dep.name(), dep.source_id()); + if dep.source_id().is_registry() { + bail!("crates cannot be published to crates.io with dependencies sourced from other\n\ + registries either publish `{}` on crates.io or pull it into this repository\n\ + and specify it with a path and version\n\ + (crate `{}` is pulled from {}", dep.name(), dep.name(), dep.source_id()); + } else { + bail!("crates cannot be published to crates.io with dependencies sourced from \ + a repository\neither publish `{}` as its own crate on crates.io and \ + specify a crates.io version as a dependency or pull it into this \ + repository and specify it with a path and version\n(crate `{}` has \ + repository path `{}`)", dep.name(), dep.name(), dep.source_id()); + } } } Ok(()) @@ -205,7 +212,7 @@ pub fn registry(config: &Config, src.update().chain_err(|| { format!("failed to update {}", sid) })?; - (src.config()?).unwrap().api + (src.config()?).unwrap().api.unwrap() }; let handle = http_handle(config)?; Ok((Registry::new_handle(api_host, token, handle), sid)) diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index c967e2ebc..8b907f7d3 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -198,7 +198,7 @@ pub struct RegistryConfig { /// API endpoint for the registry. This is what's actually hit to perform /// operations like yanks, owner modifications, publish new crates, etc. - pub api: String, + pub api: Option, } #[derive(Deserialize)] diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index 6704282b8..846226107 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -166,8 +166,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { let _lock = self.index_path.open_rw(Path::new(INDEX_LOCK), self.config, "the registry index")?; - self.config.shell().status("Updating", - format!("registry `{}`", self.source_id.url()))?; + self.config.shell().status("Updating", self.source_id.display_registry())?; // git fetch origin master let url = self.source_id.url(); diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 38e2e303f..252d8cb01 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -762,7 +762,7 @@ impl ConfigValue { } } - fn expected(&self, wanted: &str, key: &str) -> CargoResult { + pub fn expected(&self, wanted: &str, key: &str) -> CargoResult { Err(format!("expected a {}, but found a {} for `{}` in {}", wanted, self.desc(), key, self.definition_path().display()).into()) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index c2220ac2d..487d46f93 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -14,7 +14,7 @@ use url::Url; use core::{SourceId, Profiles, PackageIdSpec, GitReference, WorkspaceConfig}; use core::{Summary, Manifest, Target, Dependency, PackageId}; -use core::{EitherManifest, VirtualManifest, Features}; +use core::{EitherManifest, VirtualManifest, Features, Feature}; use core::dependency::{Kind, Platform}; use core::manifest::{LibKind, Profile, ManifestMetadata}; use sources::CRATES_IO; @@ -184,6 +184,7 @@ impl<'de> de::Deserialize<'de> for TomlDependency { #[derive(Deserialize, Serialize, Clone, Debug, Default)] pub struct DetailedTomlDependency { version: Option, + registry: Option, path: Option, git: Option, branch: Option, @@ -429,6 +430,7 @@ struct Context<'a, 'b> { warnings: &'a mut Vec, platform: Option, root: &'a Path, + features: &'a Features, } impl TomlManifest { @@ -511,6 +513,11 @@ impl TomlManifest { let mut warnings = vec![]; let mut errors = vec![]; + // Parse features first so they will be available when parsing other parts of the toml + let empty = Vec::new(); + let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); + let features = Features::new(&cargo_features, &mut warnings)?; + let project = me.project.as_ref().or_else(|| me.package.as_ref()); let project = project.ok_or_else(|| { CargoError::from("no `package` or `project` section found.") @@ -551,6 +558,7 @@ impl TomlManifest { nested_paths: &mut nested_paths, config: config, warnings: &mut warnings, + features: &features, platform: None, root: package_root, }; @@ -649,9 +657,6 @@ impl TomlManifest { }; let profiles = build_profiles(&me.profile); let publish = project.publish.unwrap_or(true); - let empty = Vec::new(); - let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty); - let features = Features::new(cargo_features, &mut warnings)?; let mut manifest = Manifest::new(summary, targets, exclude, @@ -721,6 +726,7 @@ impl TomlManifest { config: config, warnings: &mut warnings, platform: None, + features: &Features::default(), // @alex: is this right? root: root }; (me.replace(&mut cx)?, me.patch(&mut cx)?) @@ -867,8 +873,12 @@ impl TomlDependency { } } - let new_source_id = match (details.git.as_ref(), details.path.as_ref()) { - (Some(git), maybe_path) => { + let new_source_id = match (details.git.as_ref(), details.path.as_ref(), details.registry.as_ref()) { + (Some(_), _, Some(_)) => bail!("dependency ({}) specification is ambiguous. \ + Only one of `git` or `registry` is allowed.", name), + (_, Some(_), Some(_)) => bail!("dependency ({}) specification is ambiguous. \ + Only one of `path` or `registry` is allowed.", name), + (Some(git), maybe_path, _) => { if maybe_path.is_some() { let msg = format!("dependency ({}) specification is ambiguous. \ Only one of `git` or `path` is allowed. \ @@ -895,7 +905,7 @@ impl TomlDependency { let loc = git.to_url()?; SourceId::for_git(&loc, reference)? }, - (None, Some(path)) => { + (None, Some(path), _) => { cx.nested_paths.push(PathBuf::from(path)); // If the source id for the package we're parsing is a path // source, then we normalize the path here to get rid of @@ -913,7 +923,11 @@ impl TomlDependency { cx.source_id.clone() } }, - (None, None) => SourceId::crates_io(cx.config)?, + (None, None, Some(registry)) => { + cx.features.require(Feature::alternative_registries())?; + SourceId::alt_registry(cx.config, registry)? + } + (None, None, None) => SourceId::crates_io(cx.config)?, }; let version = details.version.as_ref().map(|v| &v[..]); diff --git a/tests/alt-registry.rs b/tests/alt-registry.rs new file mode 100644 index 000000000..9d701ba6b --- /dev/null +++ b/tests/alt-registry.rs @@ -0,0 +1,145 @@ +extern crate cargotest; +extern crate hamcrest; + +use cargotest::ChannelChanger; +use cargotest::support::registry::{self, Package}; +use cargotest::support::{project, execs}; +use hamcrest::assert_that; + +#[test] +fn is_feature_gated() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" feature `alternative-registries` is required")); +} + +#[test] +fn depend_on_alt_registry() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr(&format!("\ +[UPDATING] registry `{reg}` +[DOWNLOADING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] foo v0.0.1 ({dir}) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs +", + dir = p.url(), + reg = registry::alt_registry()))); + + assert_that(p.cargo("clean").masquerade_as_nightly_cargo(), execs().with_status(0)); + + // Don't download a second time + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr(&format!("\ +[COMPILING] bar v0.0.1 (registry `file://[..]`) +[COMPILING] foo v0.0.1 ({dir}) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs +", + dir = p.url()))); +} + +#[test] +fn registry_incompatible_with_path() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" dependency (bar) specification is ambiguous. Only one of `path` or `registry` is allowed.")); +} + +#[test] +fn registry_incompatible_with_git() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + git = "" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + assert_that(p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101) + .with_stderr_contains(" dependency (bar) specification is ambiguous. Only one of `git` or `registry` is allowed.")); +} + + +#[test] +fn cannot_publish_with_registry_dependency() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["alternative-registries"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + version = "0.0.1" + registry = "alternative" + "#) + .file("src/main.rs", "fn main() {}"); + p.build(); + + Package::new("bar", "0.0.1").alternative(true).publish(); + + assert_that(p.cargo("publish").masquerade_as_nightly_cargo() + .arg("--index").arg(registry::alt_registry().to_string()), + execs().with_status(101)); +} diff --git a/tests/bad-config.rs b/tests/bad-config.rs index cff576dbb..30746f0c9 100644 --- a/tests/bad-config.rs +++ b/tests/bad-config.rs @@ -794,7 +794,7 @@ fn bad_source_config2() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: could not find a configured source with the name `bar` \ @@ -826,7 +826,7 @@ fn bad_source_config3() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: detected a cycle of `replace-with` sources, [..] @@ -861,7 +861,7 @@ fn bad_source_config4() { error: failed to load source for a dependency on `bar` Caused by: - Unable to update registry https://[..] + Unable to update registry `https://[..]` Caused by: detected a cycle of `replace-with` sources, the source `crates-io` is \ diff --git a/tests/cargotest/support/registry.rs b/tests/cargotest/support/registry.rs index 3046d2e20..1e0b28cd0 100644 --- a/tests/cargotest/support/registry.rs +++ b/tests/cargotest/support/registry.rs @@ -18,6 +18,8 @@ pub fn registry_path() -> PathBuf { paths::root().join("registry") } pub fn registry() -> Url { Url::from_file_path(&*registry_path()).ok().unwrap() } pub fn dl_path() -> PathBuf { paths::root().join("dl") } pub fn dl_url() -> Url { Url::from_file_path(&*dl_path()).ok().unwrap() } +pub fn alt_registry_path() -> PathBuf { paths::root().join("alternative-registry") } +pub fn alt_registry() -> Url { Url::from_file_path(&*alt_registry_path()).ok().unwrap() } pub struct Package { name: String, @@ -28,6 +30,7 @@ pub struct Package { yanked: bool, features: HashMap>, local: bool, + alternative: bool, } struct Dependency { @@ -54,7 +57,10 @@ pub fn init() { [source.dummy-registry] registry = '{reg}' - "#, reg = registry()).as_bytes())); + + [registries.alternative] + index = '{alt}' + "#, reg = registry(), alt = alt_registry()).as_bytes())); // Init a new registry repo(®istry_path()) @@ -63,6 +69,14 @@ pub fn init() { "#, dl_url())) .build(); fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap(); + + // Init an alt registry + repo(&alt_registry_path()) + .file("config.json", &format!(r#" + {{"dl":"{0}","api":"{0}"}} + "#, dl_url())) + .build(); + fs::create_dir_all(dl_path().join("api/v1/crates")).unwrap(); } impl Package { @@ -77,6 +91,7 @@ impl Package { yanked: false, features: HashMap::new(), local: false, + alternative: false, } } @@ -85,6 +100,11 @@ impl Package { self } + pub fn alternative(&mut self, alternative: bool) -> &mut Package { + self.alternative = alternative; + self + } + pub fn file(&mut self, name: &str, contents: &str) -> &mut Package { self.files.push((name.to_string(), contents.to_string())); self @@ -174,11 +194,13 @@ impl Package { _ => format!("{}/{}/{}", &self.name[0..2], &self.name[2..4], self.name), }; + let registry_path = if self.alternative { alt_registry_path() } else { registry_path() }; + // Write file/line in the index let dst = if self.local { - registry_path().join("index").join(&file) + registry_path.join("index").join(&file) } else { - registry_path().join(&file) + registry_path.join(&file) }; let mut prev = String::new(); let _ = File::open(&dst).and_then(|mut f| f.read_to_string(&mut prev)); @@ -188,7 +210,7 @@ impl Package { // Add the new file to the index if !self.local { - let repo = t!(git2::Repository::open(®istry_path())); + let repo = t!(git2::Repository::open(®istry_path)); let mut index = t!(repo.index()); t!(index.add_path(Path::new(&file))); t!(index.write()); -- 2.30.2